package com.tistroy.admin.common.util;

import java.lang.Character.UnicodeBlock;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 문자열 처리용 유틸리티 클래스
 * 
 * @author nayasis
 */
public abstract class StringUtil {

	/**
	 * 문자열이 비어있는지 여부를 확인한다.
	 * 
	 * <pre>
	 * 1. 문자열이 null 이면 true
	 * 2. 문자열이 "" 이면 true
	 * 3. 문자열이 빈칸만으로 구성되어 있어도 true
	 * </pre>
	 * 
	 * @param string
	 *            검사할 문자열
	 * @return 비어있는지 여부
	 */
	public static boolean isEmpty(Object string) {

		return string == null || string.toString().trim().length() == 0;

	}

	/**
	 * 문자열을 주어진 포맷에 맞춰 출력한다.
	 * 
	 * 포맷은 '@' 문자를 치환가능문자로 사용하며, '@' 문자를 그대로 출력하고 싶을 경우 '\\@'이라고 적어주어야 한다.
	 * 
	 * <pre>
	 * StringUtil.format( "@는 사람입니다.", "박태민" ); --> "박태민은 사람입니다."
	 * StringUtil.format( "@는 사람입니다.", "ABC"    ); --> "ABC는 사람입니다."
	 * StringUtil.format( "@는 사람입니다." );           --> "는 사람입니다."
	 * StringUtil.format( "사람입니다." );               --> "사람입니다."
	 * </pre>
	 * 
	 * @param format
	 *            문자열 포맷
	 * @param param
	 *            '@' 문자를 치환할 파라미터
	 * @return 포맷에 맞는 문자열
	 */
	public static String format(String format, Object... param) {

		if (isEmpty(format))
			return "";

		int paramLength = param.length;
		int paramIndex = 0;

		StringBuffer cf = new StringBuffer();

		for (int i = 0, iCnt = format.length(); i < iCnt; i++) {

			char c = format.charAt(i);

			if (c == '@') {

				// "\\@"으로 입력될 경우 '@'문자를 치환하지 않고 그대로 출력
				if (i > 0 && format.charAt(i - 1) == '\\') {
					cf.deleteCharAt(cf.length() - 1);
					cf.append(c);
					continue;

					// 치환할 파라미터가 없다면 '@' 문자를 '' 로 출력
				} else if (paramLength == 0 || paramIndex == paramLength) {
					continue;

					// '@' 문자를 파라미터로 치환
				} else {

					String paramToAppend = (param[paramIndex] == null) ? "null" : param[paramIndex].toString();

					// 파라미터가 한글이고 받침이 있을 경우
					// [은/는,이/가, 을/를] 변경처리 수행
					char[] lastChar = getLastKrChar(paramToAppend);
					if (lastChar.length == 3 && (i - 1 < iCnt)) {

						char nextC = format.charAt(i + 1);
						char nextAppend = ' ';

						boolean hasThirdChar = (lastChar[2] != '\0');

						switch (nextC) {

						case '은':
						case '는':
							nextAppend = hasThirdChar ? '은' : '는';
							break;
						case '이':
						case '가':
							nextAppend = hasThirdChar ? '이' : '가';
							break;
						case '을':
						case '를':
							nextAppend = hasThirdChar ? '을' : '를';
							break;

						}

						cf.append(paramToAppend);

						if (nextAppend != ' ') {
							cf.append(nextAppend);
							i++;
						}

						// 파라미터가 한글이 아니거나, 또는 한글이지만 받침이 없을 경우
					} else {
						cf.append(paramToAppend);
					}

					paramIndex++;
				}

			} else {

				cf.append(c);
			}

		}

		// 결과값 반환
		return cf.toString();

	}

	/**
	 * 
	 * 입력한 문자열 앞뒤에 특정문자를 Left Padding한 문자열을 반환한다.
	 * 
	 * <pre>
	 * 
	 * [사용 예제]
	 * 
	 * lpad("AAAAAA", 'Z', 10) ) ===> ZZZZAAAAAA
	 * 
	 * </pre>
	 * 
	 * @param value
	 *            조작할 문자열
	 * @param padChar
	 *            PADDING 문자
	 * @param length
	 *            결과 문자열 길이
	 * @return String Padding 된 문자열
	 */
	public static String lpad(Object value, char padChar, int length) {

		String text = (value == null) ? "null" : value.toString();
		int textCharCnt = text.length();
		int index = Math.max(length - textCharCnt, 0);

		char[] result = new char[length];

		for (int i = 0; i < index; i++) {
			result[i] = padChar;
		}

		for (int i = 0, iCnt = Math.min(length, textCharCnt); i < iCnt; i++) {

			result[index + i] = text.charAt(i);

		}

		return new String(result);

	}

	/**
	 * 
	 * 입력한 문자열 앞뒤에 특정문자를 Right Pading한 문자열을 반환한다.
	 * 
	 * <pre>
	 * 
	 * [사용 예제]
	 * 
	 * rpad("AAAAAA", 'Z', 10) )    ===> AAAAAAZZZZ
	 * 
	 * </pre>
	 * 
	 * @param value
	 *            조작할 문자열
	 * @param padChar
	 *            PADDING 문자
	 * @param length
	 *            결과 문자열 길이
	 * @return String Padding 된 문자열
	 */
	public static String rpad(Object value, char padChar, int length) {

		String text = (value == null) ? "null" : value.toString();
		int index = Math.min(length, text.length());

		char[] result = new char[length];

		for (int i = 0; i < index; i++) {
			result[i] = text.charAt(i);
		}

		for (int i = index; i < length; i++) {
			result[i] = padChar;
		}

		return new String(result);

	}

	/**
	 * 입력값이 null일 경우 공백문자로 치환받는다.
	 * 
	 * @param val
	 *            입력값
	 * @return NVL 문자열
	 */
	public static String nvl(Object val) {
		if (val == null) {
			return "";
		} else {
			return val.toString();
		}
	}

	/**
	 * 입력값이 null일 경우, 지정한 문자로 치환받는다.
	 * 
	 * @param val
	 *            입력값
	 * @param nvlValue
	 *            지정한 문자
	 * @return NVL 문자열
	 */
	public static String nvl(Object val, Object nvlValue) {

		if (val == null) {
			return nvl(nvlValue);
		} else {
			return val.toString();
		}

	}

	/**
	 * 문자열을 Camel 형으로 변환한다.
	 * 
	 * <pre>
	 * String text = DataConverter.getCamel( "unicode_text" );
	 * System.out.println( text ); --> "unicodeText" 가 출력됨
	 * </pre>
	 * 
	 * @param param
	 *            변환할 문자열
	 * @return CAMEL 형 문자열
	 * @author 정화수
	 */
	public static String getCamel(String param) {

		if (isEmpty(param))
			return "";

		param = param.toLowerCase();
		Pattern pattern = Pattern.compile("(_[a-zA-Z])");
		Matcher matcher = pattern.matcher(param);
		StringBuffer sb = new StringBuffer();

		while (matcher.find()) {

			String r = matcher.group().substring(1);
			if (matcher.start() != 0)
				r = r.toUpperCase();

			matcher.appendReplacement(sb, r);

		}

		matcher.appendTail(sb);

		return sb.toString();

	}

	/**
	 * CAMEL형 문자열을 _ 형 문자열로 변환한다.
	 * 
	 * <pre>
	 * String text = DataConverter.getUnCamel( "unicodeText" );
	 * System.out.println( text ); --> "unicode_text" 가 출력됨
	 * </pre>
	 * 
	 * @param param
	 *            변환할 문자열
	 * @return UNDERLINE 형 문자열
	 * @author 정화수
	 * @since 2011-05-02
	 */
	public static String getUnCamel(String param) {

		Pattern pattern = Pattern.compile("([A-Z])");
		Matcher matcher = pattern.matcher(param);
		StringBuffer sb = new StringBuffer();

		while (matcher.find()) {

			if (matcher.start() == 0)
				continue;

			String r = matcher.group();
			matcher.appendReplacement(sb, "_" + r.toLowerCase());

		}

		matcher.appendTail(sb);

		return sb.toString();

	}

	/**
	 * JSON 구조가 깨지지 않게끔 텍스트를 가공한다.
	 * 
	 * @param param
	 * @return 특수문자가 제거된 JSON 텍스트
	 * @author 정화수
	 * @since 2011-04-07
	 */
	public static String escapeJson(Object param) {

		if (param == null)
			return "";

		String tempTxt = param.toString();

		StringBuffer sb = new StringBuffer();

		for (int inx = 0, inxCnt = tempTxt.length(); inx < inxCnt; inx++) {

			char ch = tempTxt.charAt(inx);

			switch (ch) {
			case '"':
				sb.append("\\\"");
				break;
			case '\\':
				sb.append("\\\\");
				break;
			case '\b':
				sb.append("\\b");
				break;
			case '\f':
				sb.append("\\f");
				break;
			case '\n':
				sb.append("\\n");
				break;
			case '\r':
				sb.append("\\r");
				break;
			case '\t':
				sb.append("\\t");
				break;
			case '/':
				sb.append("\\/");
				break;
			default:
				if (ch >= '\u0000' && ch <= '\u001F') {
					String ss = Integer.toHexString(ch);
					sb.append("\\u");
					for (int k = 0; k < 4 - ss.length(); k++) {
						sb.append('0');
					}
					sb.append(ss.toUpperCase());
				} else {
					sb.append(ch);
				}
			}
		}

		return sb.toString();

	}

	/**
	 * \\ 문자를 \ 문자로 변경해 텍스트를 원복시킨다.
	 * 
	 * @param param
	 * @return 가공된 문자가 원복된 텍스트
	 * @author 정화수
	 * @since 2011-10-13
	 */
	public static String unescapeJson(Object param) {

		if (param == null)
			return "";

		String srcTxt = param.toString();

		int len = srcTxt.length();

		StringBuffer sb = new StringBuffer(len);

		for (int i = 0; i < len - 1; i++) {

			char c = srcTxt.charAt(i);

			if (c == '\\') {

				char nc = srcTxt.charAt(i + 1);

				switch (nc) {

				case 'b':
					sb.append('\b');
					i++;
					continue;
				case 'f':
					sb.append('\f');
					i++;
					continue;
				case 'n':
					sb.append('\n');
					i++;
					continue;
				case 'r':
					sb.append('\r');
					i++;
					continue;
				case 't':
					sb.append('\t');
					i++;
					continue;

				}

			}

			sb.append(c);

		}

		sb.append(srcTxt.charAt(len - 1));

		return sb.toString();
	}

	/**
	 * \\u**** 형식의 문자열을 unicode 문자열로 변경한다.
	 * 
	 * <pre>
	 * StringUtils.unescapeJava( "\uacb0\uc7ac\uae08\uc561\uc624\ub958" );
	 * 
	 * -> "결제금액오류"
	 * </pre>
	 * 
	 * @param param
	 *            문자열
	 * @return 유니코드 문자열
	 */
	public static String unescapeJava(Object param) {

		if (param == null)
			return "";

		String srcTxt = param.toString();

		Pattern pattern = Pattern.compile("\\\\[u|U][0-9a-fA-F]{4}");

		Matcher matcher = pattern.matcher(srcTxt);

		StringBuffer sb = new StringBuffer(srcTxt.length());

		while (matcher.find()) {

			String findedKeyword = matcher.group();

			matcher.appendReplacement(sb, Matcher.quoteReplacement(getUnescapedChar(findedKeyword)));

		}

		matcher.appendTail(sb);

		return sb.toString();

	}

	private static String getUnescapedChar(String unicode) {

		String hex = unicode.substring(2, 6);

		int hexNumber = Integer.parseInt(hex, 16);

		return Character.toString((char) hexNumber);

	}

	/**
	 * 리스트 객체를 문자열로 만든다.
	 * 
	 * <pre>
	 * List<String> list = new ArrayList(){{add("a");add("b");add("c");}};
	 * 
	 * StringUtil.join( list, "," );
	 * 
	 * --> a,b,c
	 * </pre>
	 * 
	 * @param list
	 *            리스트 객체
	 * @param delimeter
	 *            엘리먼트 사이를 연결시킬 구분 문자열
	 * @return
	 */
	public static String join(List<?> list, String delimeter) {

		if (list == null)
			return null;

		StringBuffer sb = new StringBuffer();

		for (int i = 0, iCnt = list.size() - 1; i <= iCnt; i++) {

			sb.append(sb);

			if (i != iCnt) {
				sb.append(delimeter);
			}

		}

		return sb.toString();

	}

	/**
	 * 문자열의 마지막 글자가 한글일 경우 초성/중성/종성이 분리되어 담긴 배열을 구한다.
	 * 
	 * <pre>
	 * 
	 * StringUtil.getLastKrChar( "메롱" ); -> [ 'ㄹ','ㅗ','ㅇ'] 을 반환
	 * StringUtil.getLastKrChar( "정화수" ); -> ['ㅅ','ㅜ','\0' ] 을 반환
	 * StringUtil.getLastKrChar( "메롱Han" ); -> null 을 반환
	 * 
	 * </pre>
	 * 
	 * @param string
	 *            검사할 문자열
	 * @return 한글일 경우 초성/중성/종성 순으로 문자가 분리된 배열, 한글이 아닐 경우 길이가 0인 char 배열을 반환
	 * @author 정화수
	 * @since 2011-06-20
	 */
	public static char[] getLastKrChar(String string) {

		if (string == null || string.length() == 0)
			return new char[0];

		char[] HAN_1ST = new char[] { 'ㄱ', 'ㄲ', 'ㄴ', 'ㄷ', 'ㄸ', 'ㄹ', 'ㅁ', 'ㅂ', 'ㅃ', 'ㅅ', 'ㅆ', 'ㅇ', 'ㅈ', 'ㅉ', 'ㅊ', 'ㅋ',
				'ㅌ', 'ㅍ', 'ㅎ' };
		char[] HAN_2ND = new char[] { 'ㅏ', 'ㅐ', 'ㅑ', 'ㅒ', 'ㅓ', 'ㅔ', 'ㅕ', 'ㅖ', 'ㅗ', 'ㅘ', 'ㅙ', 'ㅚ', 'ㅛ', 'ㅜ', 'ㅝ', 'ㅞ',
				'ㅟ', 'ㅠ', 'ㅡ', 'ㅢ', 'ㅣ' };
		char[] HAN_3RD = new char[] { '\0', 'ㄱ', 'ㄲ', 'ㄳ', 'ㄴ', 'ㄵ', 'ㄶ', 'ㄷ', 'ㄹ', 'ㄺ', 'ㄻ', 'ㄼ', 'ㄽ', 'ㄾ', 'ㄿ', 'ㅀ',
				'ㅁ', 'ㅂ', 'ㅄ', 'ㅅ', 'ㅆ', 'ㅇ', 'ㅈ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ' };

		char lastChar = string.charAt(string.length() - 1);

		if (lastChar < 0xAC00 || lastChar > 0xD79F)
			return new char[0];

		lastChar -= 0xAC00;

		int idx3rd = lastChar % 28;
		int idx2nd = ((lastChar - idx3rd) / 28) % 21;
		int idx1st = ((lastChar - idx3rd) / 28) / 21;

		char[] result = new char[3];

		result[0] = HAN_1ST[idx1st];
		result[1] = HAN_2ND[idx2nd];
		result[2] = HAN_3RD[idx3rd];

		return result;

	}

	/**
	 * <pre>
	 * 
	 * 전화번호에 지역번호, 국번, 번호를 구분해 -를 붙여서 return 해주는 메소드
	 * 
	 * </pre>
	 * 
	 * @param phoneNumber
	 *            전화번호
	 * @return - 기호로 구분된 전화번호
	 * @author nayasis
	 * @since 2011-08-09
	 */
	public static String getPhoneNumber(String phoneNumber) {

		if (phoneNumber == null)
			return "";

		return phoneNumber.replaceAll("\\D*(02|\\d{3})\\D*(\\d{3,4})\\D*(\\d{4})", "$1-$2-$3");

	}

	/**
	 * 문자열의 첫글자를 소문자로 바꾼다.
	 * 
	 * @param text
	 *            처리할 문자열
	 * @return 변환된 문자열
	 */
	public static String lowerFirst(String text) {
		if (text == null || text.length() < 1)
			return text;
		return (text.substring(0, 1).toLowerCase() + text.substring(1));
	}

	/**
	 * 문자열의 첫글자를 대문자로 바꾼다.
	 * 
	 * @param text
	 *            처리할 문자열
	 * @return 변환된 문자열
	 */
	public static String upperFirst(String text) {
		if (text == null || text.length() < 1)
			return text;
		return (text.substring(0, 1).toUpperCase() + text.substring(1));
	}

	/**
	 * 문자열에서 숫자만 추출한다.
	 * 
	 * @param string
	 *            작업할 대상 문자열
	 * @return 숫자만 추출된 문자열
	 */
	public static String getNum(String string) {

		if (string == null || "".equals(string))
			return "";

		return string.replaceAll("[^0-9]", "");

	}

	/**
	 * 에러를 문자열로 만든다.
	 * 
	 * @param exception
	 *            에러
	 * @return 문자열
	 */
	public static StringBuffer getStackTrace(Throwable exception) {

		StringBuffer sb = new StringBuffer();

		if (exception == null)
			return sb;

		StackTraceElement[] stackTrace = exception.getStackTrace();

		sb.append(exception).append('\n');

		for (StackTraceElement trace : stackTrace) {
			sb.append("\tat ").append(trace).append('\n');
		}

		Throwable cause = exception.getCause();

		if (cause != null) {

			sb.append("Caused By : ");
			sb.append(getStackTrace(cause));
		}

		return sb;

	}

	/**
	 * ID 마스크 처리 끝 2자리를 * 처리한다.
	 * 
	 * @param id
	 *            : 아이디
	 * @return String
	 */
	public static String cvtIdMask(String id) {
		if (id == null)
			return "";

		if (id.length() <= 2)
			return id;

		id = id.substring(0, id.length() - 2) + "**";

		return id;
	}

	/**
	 * 한글이름 마스크 처리 (해외이름/국내이름 모두 사용가능) 첫자와 마지막자만 보이게.
	 * 
	 * @param kName
	 *            : 한글 이름
	 * @return String
	 */
	public static String cvtKreanNameMask(String kName) {
		if (kName == null)
			return "";

		if (kName.length() <= 1)
			return kName;

		// 한글이름이면
		if (containsHangul(kName)) {
			if (kName.length() == 2) {
				kName = kName.substring(0, 1) + "*";
			} else {
				kName = kName.substring(0, 1) + convStar(kName.length() - 2)
						+ kName.substring(kName.length() - 1, kName.length());
				;
			}
		} else {
			kName = cvtForeignNameMask(kName);
		}

		return kName;
	}

	/**
	 * 해외이름 마스크 처리 (해외이름만 사용가능) 4번째부터 3글자.
	 * 
	 * @param fName
	 *            : 해외 이름
	 * @return String
	 */
	public static String cvtForeignNameMask(String fName) {
		if (fName == null)
			return "";

		if (fName.length() <= 3)
			return fName;

		String maskName = fName.substring(0, 3) + convFstar(fName.length());
		if (fName.length() > 6)
			maskName = maskName + fName.substring(6, fName.length());
		return maskName;
	}

	/**
	 * 전화번호(일반전화,Mobile) 마스크 처리 가운데 자리를 * 처리한다.
	 * 
	 * @param sProdNo
	 *            : 전화번호
	 * @return String
	 */
	public static String cvtProdNoMask(String sProdNo) {
		if (sProdNo == null)
			return "";

		sProdNo = replace(sProdNo, "-", "");
		if (sProdNo.length() <= 7)
			return sProdNo;

		if (sProdNo.startsWith("02")) {
			if (sProdNo.length() == 9) {
				sProdNo = sProdNo.substring(0, 2) + "-" + "***" + "-" + sProdNo.substring(5);
			} else if (sProdNo.length() == 10) {
				sProdNo = sProdNo.substring(0, 2) + "-" + "****" + "-" + sProdNo.substring(6);
			}

		} else {
			if (sProdNo.length() == 10) {
				sProdNo = sProdNo.substring(0, 3) + "-" + "***" + "-" + sProdNo.substring(6);
			} else if (sProdNo.length() == 11) {
				sProdNo = sProdNo.substring(0, 3) + "-" + "****" + "-" + sProdNo.substring(7);
			}

		}

		return sProdNo;
	}

	/**
	 * 한글이 한자라도 있으면 한글(이름)으로 인식.
	 * 
	 * @param str
	 *            : 이름.
	 * @return String
	 */
	public static boolean containsHangul(String str) {
		for (int i = 0; i < str.length(); i++) {
			char ch = str.charAt(i);
			Character.UnicodeBlock unicodeBlock = Character.UnicodeBlock.of(ch);
			if (UnicodeBlock.HANGUL_SYLLABLES.equals(unicodeBlock)
					|| UnicodeBlock.HANGUL_COMPATIBILITY_JAMO.equals(unicodeBlock)
					|| UnicodeBlock.HANGUL_JAMO.equals(unicodeBlock)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * convStar.
	 * 
	 * @param lengthSize
	 *            : 한글 이름
	 * @return String
	 */
	public static String convStar(int lengthSize) {

		if (lengthSize < 1) {
			return "";
		}

		StringBuffer tochars = new StringBuffer(lengthSize);
		for (int i = 0; i < lengthSize; i++) {
			tochars.append("*");
		}

		return tochars.toString();
	}

	/**
	 * convFstar.
	 * 
	 * @param lengthSize
	 *            lengthSize
	 * @return String
	 */
	public static String convFstar(int lengthSize) {

		if (lengthSize < 3) {
			return "";
		}

		String toStar = "";
		if (lengthSize == 4) {
			toStar = "*";
		} else if (lengthSize == 5) {
			toStar = "**";
		} else if (lengthSize >= 6) {
			toStar = "***";
		}
		return toStar;
	}

	/**
	 * 주어진 문자열에서 지정한 문자열값을 지정한 문자열로 치환후 그결과 문자열을 리턴함.
	 * 
	 * @param src
	 *            src
	 * @param from
	 *            from
	 * @param to
	 *            to
	 * @return 문자열
	 */
	public static String replace(String src, String from, String to) {
		if (src == null)
			return null;
		if (from == null)
			return src;
		if (to == null)
			to = "";
		StringBuffer buf = new StringBuffer();
		for (int pos; (pos = src.indexOf(from)) >= 0;) {
			buf.append(src.substring(0, pos));
			buf.append(to);
			src = src.substring(pos + from.length());
		}
		buf.append(src);
		return buf.toString();
	}

	/**
	 * 현재 서버의 IP 주소를 가져옵니다.
	 * 
	 * @return IP 주소
	 */
	public static String getLocalServerIp() {
		try {
			for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) {
				NetworkInterface intf = en.nextElement();
				for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements();) {
					InetAddress inetAddress = enumIpAddr.nextElement();
					if (!inetAddress.isLoopbackAddress() && !inetAddress.isLinkLocalAddress()
							&& inetAddress.isSiteLocalAddress()) {
						return inetAddress.getHostAddress().toString();
					}
				}
			}
		} catch (SocketException ex) {
		}
		return null;
	}
}
